﻿/*
 * 
 * The base for this class was submitted by user TalentedMaverick on the Betfair Developer Program Forum
 * http://forum.bdp.betfair.com/showthread.php?t=163 Cudos to him!
 *  
*/

using System;
using System.Collections.Generic;
using betfairfind.Betfair.UK;
using System.Globalization;

namespace betfairfind
{
    public class MarketPricesCompressed
    {
        public enum UpdateTypeEnum { Standard, Complete }

        public double TotalMatched { get; set;}

        // Classes
        public class SelectionPricesInfo
        {
            // Classes
            public struct OddsAvailablePriceInfo
            {
                public double Odds;
                public double BackAmountAvailable;
                public double LayAmountAvailable;
            }

            // Members
            public /*readonly*/ int SelectionId;
            public /*readonly*/ int OrderIndex;
            public /*readonly*/ double TotalAmountMatched;
            public /*readonly*/ double LastPriceMatched;
            public /*readonly*/ double ReductionFactor;
            public /*readonly*/ bool Vacant;
            public /*readonly*/ int AsianLineId;
            public /*readonly*/ double FarSPPrice;
            public /*readonly*/ double NearSPPrice;
            public /*readonly*/ double ActualSPPrice;
            public /*readonly*/ double Handicap;

            // The price data.
            public readonly SortedList<double, OddsAvailablePriceInfo> PriceInfo = new SortedList<double, OddsAvailablePriceInfo>();

            // Construction
            public SelectionPricesInfo() { }

            public SelectionPricesInfo(string data)
            {
                int iterator = -1;

                // First extract the fields
                SelectionId = Convert.ToInt32(NextField(data, '~', ref iterator));
                OrderIndex = Convert.ToInt32(NextField(data, '~', ref iterator));
                TotalAmountMatched = ConvertToDouble(NextField(data, '~', ref iterator));
                LastPriceMatched = ConvertToDouble(NextField(data, '~', ref iterator));
                Handicap = ConvertToDouble(NextField(data, '~', ref iterator));
                ReductionFactor = ConvertToDouble(NextField(data, '~', ref iterator));
                Vacant = NextField(data, '~', ref iterator).ToLower() == "true" ? true : false;
                FarSPPrice = ConvertToDouble(NextField(data, '~', ref iterator));
                NearSPPrice = ConvertToDouble(NextField(data, '~', ref iterator));
                ActualSPPrice = ConvertToDouble(NextField(data, '~', ref iterator));

                // Back Prices
                NextField(data, '|', ref iterator);

                while (data[iterator + 1] != '|')
                    SetPrice(data, ref iterator);

                // Lay Prices
                iterator++;

                while (iterator + 1 < data.Length)
                    SetPrice(data, ref iterator);
            }

            private void SetPrice(String compressed_prices, ref int compressed_iterator)
            {
                OddsAvailablePriceInfo OddsAvailablePriceInfo = new OddsAvailablePriceInfo();

                OddsAvailablePriceInfo.Odds = ConvertToDouble(NextField(compressed_prices, '~', ref compressed_iterator));
                double amountAvailable = ConvertToDouble(NextField(compressed_prices, '~', ref compressed_iterator));

                String bet_type = NextField(compressed_prices, '~', ref compressed_iterator);
                int depth = Convert.ToInt32(NextField(compressed_prices, '~', ref compressed_iterator));

                if (bet_type == "B")
                    OddsAvailablePriceInfo.LayAmountAvailable = amountAvailable;
                else
                    OddsAvailablePriceInfo.BackAmountAvailable = amountAvailable;

                PriceInfo.Add(OddsAvailablePriceInfo.Odds, OddsAvailablePriceInfo);
            }

            // Back & Lay Prices.  
            // If count = 3 && pad == true, blank prices are added so that 3 prices are always returned.
            public List<OddsAvailablePriceInfo> BestPricesToLay(int count, bool pad)
            {
                List<OddsAvailablePriceInfo> results = new List<OddsAvailablePriceInfo>();

                int counter = 0;

                for (int c = 0; c < PriceInfo.Count && counter < count; c++)
                    if (PriceInfo.Values[c].LayAmountAvailable != 0)
                    {
                        results.Add(PriceInfo.Values[c]);
                        counter++;
                    }

                while (counter++ < count && pad)
                    results.Add(new OddsAvailablePriceInfo());

                return results;
            }

            public List<OddsAvailablePriceInfo> BestPricesToBack(int count, bool pad)
            {
                List<OddsAvailablePriceInfo> results = new List<OddsAvailablePriceInfo>();

                int counter = 0;

                for (int c = PriceInfo.Count - 1; c >= 0 && counter < count; c--)
                    if (PriceInfo.Values[c].BackAmountAvailable != 0)
                    {
                        results.Add(PriceInfo.Values[c]);
                        counter++;
                    }

                while (counter++ < count && pad)
                    results.Add(new OddsAvailablePriceInfo());

                return results;
            }

            // Call with count = 3 for All 6 prices, left to right.
            public List<OddsAvailablePriceInfo> BestPrices(int count)
            {
                List<OddsAvailablePriceInfo> results = BestPricesToBack(count, true);
                results.Reverse();
                results.AddRange(BestPricesToLay(count, true));
                return results;
            }
        }

        // Members
        public /*readonly*/ MarketStatusEnum MarketStatus = MarketStatusEnum.ACTIVE;
        public /*readonly*/ int MarketId;
        public /*readonly*/ DateTime Timestamp;
        public /*readonly*/ int InPlayDelay;
        public /*readonly*/ string RemovedRunnerInfo;
        public /*readonly*/ string CurrencyCode = "";
        public /*readonly*/ int NumberOfWinners;
        public /*readonly*/ string MarketInfo;
        public /*readonly*/ bool DiscountAllowed = false;
        public /*readonly*/ float MarketBaseRate;
        public /*readonly*/ long RefreshTimeInMilliseconds;
        public /*readonly*/ bool BspMarket;

        public readonly List<SelectionPricesInfo> CompletePriceInfo = new List<SelectionPricesInfo>();


        public double GetBestBackOdds(int runnerId)
        {
            return CompletePriceInfo[runnerId].BestPricesToBack(1, false)[0].Odds;
        }
        public double GetBestLayOdds(int runnerId)
        {
            return CompletePriceInfo[runnerId].BestPricesToLay(1, false)[0].Odds;
        }

        private void SetTotalMatched()
        {
            double ret = 0;
            foreach (var item in CompletePriceInfo)
            {
                ret += item.TotalAmountMatched;
            }
            TotalMatched = ret;
        }

        /// <summary>Creates the data type from a MarketPriceCompressed response</summary>
        public MarketPricesCompressed(GetMarketPricesCompressedResp resp)
        {
            // No data.
            if (resp == null || resp.marketPrices == null || resp.marketPrices.Length == 0)
                return;

            Timestamp = resp.header.timestamp;
            int iterator = -1;
            string data = resp.marketPrices;




            // Extract remaining market fields.
            MarketId = Convert.ToInt32(NextField(data, '~', ref iterator));
            CurrencyCode = NextField(data, '~', ref iterator);
            MarketStatus = (MarketStatusEnum)Enum.Parse(typeof(MarketStatusEnum), NextField(data, '~', ref iterator));
            InPlayDelay = Convert.ToInt32(NextField(data, '~', ref iterator));
            NumberOfWinners = Convert.ToInt32(NextField(data, '~', ref iterator));
            MarketInfo = NextField(data, '~', ref iterator);

            if (NextField(data, '~', ref iterator) == "true")
                DiscountAllowed = true;

            string a = NextField(data, '~', ref iterator);
            MarketBaseRate = Single.Parse(a, (new CultureInfo("en-GB")).NumberFormat);
            RefreshTimeInMilliseconds = Convert.ToInt64(NextField(data, '~', ref iterator));
            RemovedRunnerInfo = NextField(data, '~', ref iterator);
            BspMarket = NextField(data, ':', ref iterator).ToLower() == "y" ? true : false;

            if (InPlayDelay > 0 && MarketStatus == MarketStatusEnum.ACTIVE)
            {
                //MarketStatus = MarketStatusEnum.INPLAY;
            }

            // if no price info, quit.
            if (data.Length < iterator + 1)
                return;

            while (iterator + 1 < data.Length)
                CompletePriceInfo.Add(new SelectionPricesInfo(NextField(data, ':', ref iterator)));

            SetTotalMatched();
        }

        /// <summary>Extracts the next field on compressed calls.</summary>
        public static string NextField(string compressed_prices, char delimiter, ref int iterator)
        {
            String result;
            int index = compressed_prices.IndexOf(delimiter, iterator + 1);
            if (index != -1)
                result = compressed_prices.Substring(iterator + 1, index - iterator - 1);
            else
                result = compressed_prices.Substring(iterator + 1, compressed_prices.Length - iterator - 1);

            if (index == -1)
                iterator = compressed_prices.Length;
            else
                iterator = index;

            return result;
        }

        /// <summary>Deal with some API quirks for double conversions.</summary>
        internal static double ConvertToDouble(string data)
        {

            if (data == "Infinity")
                return Double.PositiveInfinity;

            if (data == "-Infinity")
                return Double.NegativeInfinity;

            if (data != "NaN" && data != "") return Double.Parse(data, (new CultureInfo("en-GB").NumberFormat));

            return 0;
        }
    }
}
